package com.niiwoo.civet.trade.service.local.newRefund.refundDeal.core;

import com.niiwoo.civet.base.enums.FeeTypeEnum;
import com.niiwoo.civet.base.enums.RefundTypeEnum;
import com.niiwoo.civet.trade.constant.AmountHandleConstant;
import com.niiwoo.civet.trade.dao.entity.*;
import com.niiwoo.civet.trade.dao.mapper.*;
import com.niiwoo.civet.trade.dto.newRefund.RefundFeeDTO;
import com.niiwoo.civet.trade.dto.newRefund.RefundStateDTO;
import com.niiwoo.civet.trade.enums.RefundOptTypeEnum;
import com.niiwoo.civet.trade.enums.RepayStatusEnum;
import com.niiwoo.civet.trade.service.local.newRefund.common.RefundCommonService;
import com.niiwoo.civet.trade.service.local.refund.CommonLocalService;
import com.niiwoo.civet.trade.utils.RepaymentUtil;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.MathContext;
import java.util.*;


/**
 * 还款金额计算
 */
@Service
public class FeeCalcForRefundService {

    @Autowired
    private ProjectBorrowerPlanNewMapperExt projectBorrowerPlanNewMapperExt;

    @Autowired
    private ProjectBorrowerPlanDetailMapperExt projectBorrowerPlanDetailMapperExt;

    @Autowired
    private ProjectBorrowerSummaryMapperExt projectBorrowerSummaryMapperExt;

    @Autowired
    private CommonLocalService commonLocalService;

    @Autowired
    private ProjectMapperExt projectMapperExt;

    @Autowired
    private RefundCommonService refundCommonService;

    @Autowired
    private ProjectInvestorPlanMapperExt projectInvestorPlanMapperExt;

    private static final Logger logger = LoggerFactory.getLogger(FeeCalcForRefundService.class);

    /**
     * 获取应还金额（按状态）
     * @param projectId
     * @param refundTypeEnum
     * @return
     */
    public RefundStateDTO caculateAmountRepaymentForAPP(Long projectId, RefundTypeEnum refundTypeEnum){
        ProjectBorrowerSummary summary = projectBorrowerSummaryMapperExt.selectByPrimaryKey(projectId);
        Project project = projectMapperExt.selectByPrimaryKey(projectId);
        Date currentDate = commonLocalService.getCurrentDate();
        List<RefundFeeDTO> refundingFeeDTOList = this.caculateAmount(project, summary, refundTypeEnum, RefundOptTypeEnum.getRealRefundOptTypeEnum(RefundOptTypeEnum.BY_PROJECT_STATS, refundTypeEnum),
                currentDate, currentDate,null, project.getDeadlineUnit());
        return this.convertToRefundFee(refundingFeeDTOList);
    }

    /**
     * 获取应还金额（按期数）
     * 已处理利息
     * @param projectId
     * @param periods
     * @return
     */
    public RefundStateDTO caculateAmountByPeriods(Long projectId, List<Integer> periods){
        List<ProjectBorrowerPlanNew> borrowerPlanNewList = projectBorrowerPlanNewMapperExt.selectByProjectIdAndPeriods(projectId, periods);
        List<RefundFeeDTO> refundingFeeDTOList = this.getRefundingFeeDTOList(borrowerPlanNewList, null);
        return this.convertToRefundFee(refundingFeeDTOList);
    }


    /**
     * 获取应还金额
     * 已处理利息
     * @param summary
     * @param refundTypeEnum
     * @param refundOptTypeEnum
     * @param compareDate
     * @param currentDate
     * @param periods
     * @param deadlineUnit
     * @return
     */
    public List<RefundFeeDTO> caculateAmount(Project project, ProjectBorrowerSummary summary, RefundTypeEnum refundTypeEnum,
                                             RefundOptTypeEnum refundOptTypeEnum, Date compareDate, Date currentDate,List<Integer> periods, Byte deadlineUnit){
        List<ProjectBorrowerPlanNew> borrowerPlanNewList = this.getNeedRefundProjectBorrowPlanNew(refundOptTypeEnum.getOptType(), compareDate, summary,periods);

        List<RefundFeeDTO> refundingFeeDTOList = this.getRefundingFeeDTOList(borrowerPlanNewList, null);

        Set<Integer> periodSet = new HashSet<>();
        for (RefundFeeDTO refundFeeDTO : refundingFeeDTOList) {
            periodSet.add(refundFeeDTO.getPeriod());
        }

        // 处理利息
        boolean isCriticalOverDue = RepayStatusEnum.BADLOANS.getCode().equals(summary.getRepayStatus());
        Integer currentPeriod = summary.getCurrentPeriod();
        for (RefundFeeDTO refundFeeDTO : refundingFeeDTOList) {
            if(FeeTypeEnum.INTEREST.getValue().equals(refundFeeDTO.getFeeType())){
                // 坏账期之后的待还利息不收
                if (isCriticalOverDue && refundFeeDTO.getPeriod() > currentPeriod + 1){
                    refundFeeDTO.setRefundingMoney(BigDecimal.ZERO);
                }
                // 提前结清免息
                if (RefundTypeEnum.OVERDEPT.equals(refundTypeEnum) && Byte.valueOf("1").equals(deadlineUnit)){
                    if (refundFeeDTO.getRefundDate().after(new DateTime(currentDate).plusMonths(1).minusDays(1).toDate())
                            && !Integer.valueOf("1").equals(refundFeeDTO.getPeriod())) {
                        refundFeeDTO.setReducedFee(refundFeeDTO.getRefundingMoney());
                        refundFeeDTO.setRefundingMoney(BigDecimal.ZERO);
                    }
                }

                // 如果是按日计息还款，计算借款人应还款利息、减免利息
                if(refundCommonService.isCalculateInterestByDay(project.getOrgId(), project.getDeadlineUnit(), refundFeeDTO.getRefundDate(), currentDate)){
                    // 借款人应还款利息需要从投资人应收利息进行累加
                    BigDecimal refundingMoney = calculateInterestByDay(project, currentDate, new ArrayList<>(periodSet));
                    refundFeeDTO.setReducedFee(refundFeeDTO.getRefundingMoney().subtract(refundingMoney));
                    refundFeeDTO.setRefundingMoney(refundingMoney);
                }

            }

            // 如果是按日计息还款，计算借款人应还平台管理费、担保服务费、咨询服务费
            if(refundCommonService.isCalculateInterestByDay(project.getOrgId(), project.getDeadlineUnit(), refundFeeDTO.getRefundDate(), currentDate)){
                if(FeeTypeEnum.MANAGEMENT_FEE.getValue().equals(refundFeeDTO.getFeeType())){
                    BigDecimal manageFee = calculateManageFeeByDay(project,currentDate);
                    refundFeeDTO.setReducedFee(refundFeeDTO.getRefundingMoney().subtract(manageFee));
                    refundFeeDTO.setRefundingMoney(manageFee);
                }

                if(FeeTypeEnum.GUARANTEE_FEE.getValue().equals(refundFeeDTO.getFeeType())){
                    BigDecimal guaranteeFee = calculateGuaranteeFeeByDay(project,currentDate);
                    refundFeeDTO.setReducedFee(refundFeeDTO.getRefundingMoney().subtract(guaranteeFee));
                    refundFeeDTO.setRefundingMoney(guaranteeFee);
                }

                if(FeeTypeEnum.CONSULTING_FEE.getValue().equals(refundFeeDTO.getFeeType())){
                    BigDecimal consultingFee = calculateConsultingFeeByDay(project,currentDate);
                    refundFeeDTO.setReducedFee(refundFeeDTO.getRefundingMoney().subtract(consultingFee));
                    refundFeeDTO.setRefundingMoney(consultingFee);
                }
            }
        }
        return refundingFeeDTOList;
    }

    /**
     * 获取待还还款计划列表
     * @param refundOptType
     * @param compareDate
     * @param summary
     * @param periods
     * @return
     */
    public List<ProjectBorrowerPlanNew> getNeedRefundProjectBorrowPlanNew(Byte refundOptType, Date compareDate, ProjectBorrowerSummary summary,List<Integer> periods){
        Long projectId = summary.getProjectId();
        if(RefundOptTypeEnum.OVERDEPT.getOptType().equals(refundOptType)
                || RefundOptTypeEnum.BY_SPECIFIED_AMOUNT.getOptType().equals(refundOptType)){
            return projectBorrowerPlanNewMapperExt.selectNeedRefundList(projectId, null);
        }else if(RefundOptTypeEnum.BY_COMPARE_DATE.getOptType().equals(refundOptType)){
            return projectBorrowerPlanNewMapperExt.selectNeedRefundList(projectId, compareDate);
        } else if(RefundOptTypeEnum.BY_PROJECT_STATS.getOptType().equals(refundOptType)){
            if(RepayStatusEnum.NORMAL.getCode().equals(summary.getRepayStatus())){
                return projectBorrowerPlanNewMapperExt.selectByProjectIdAndPeriods(projectId, Collections.singletonList(summary.getCurrentPeriod()));
            }else if(RepayStatusEnum.BADLOANS.getCode().equals(summary.getRepayStatus())){
                return projectBorrowerPlanNewMapperExt.selectNeedRefundList(projectId, null);
            }else{
                return projectBorrowerPlanNewMapperExt.selectNeedRefundList(projectId, compareDate);
            }
        }else if(RefundOptTypeEnum.BY_SPECIFIED_PERIODS.getOptType().equals(refundOptType) && periods!=null){
            return projectBorrowerPlanNewMapperExt.selectByProjectIdAndPeriods(projectId, periods);
        }else{
            throw new RuntimeException("参数有误");
        }
    }

    /**
     * 返回应还金额
     * @param borrowerPlanNewList
     * @param feeTypeEnum
     * @return
     */
    public List<RefundFeeDTO> getRefundingFeeDTOList(List<ProjectBorrowerPlanNew> borrowerPlanNewList, FeeTypeEnum feeTypeEnum){
        List<RefundFeeDTO> resultList = new ArrayList<>();
        List<Long> planIdList = new ArrayList<>();
        Map<Long, ProjectBorrowerPlanNew> map = new HashMap<>();
        for (ProjectBorrowerPlanNew projectBorrowerPlanNew : borrowerPlanNewList) {
            planIdList.add(projectBorrowerPlanNew.getId());
            map.put(projectBorrowerPlanNew.getId(), projectBorrowerPlanNew);
        }
        if(planIdList.size() > 0) {
            List<ProjectBorrowerPlanDetail> projectBorrowerPlanDetailList;
            if(null == feeTypeEnum){
                projectBorrowerPlanDetailList = projectBorrowerPlanDetailMapperExt.selectListByPlanIds(planIdList);
            } else{
                projectBorrowerPlanDetailList = projectBorrowerPlanDetailMapperExt.selectListByPlanIdsFeeType(planIdList, feeTypeEnum.getValue());
            }
            for (ProjectBorrowerPlanDetail projectBorrowerPlanDetail : projectBorrowerPlanDetailList) {
                ProjectBorrowerPlanNew projectBorrowerPlanNew = map.get(projectBorrowerPlanDetail.getPlanId());
                BigDecimal refundMoney = projectBorrowerPlanDetail.getRefundMoney();
                BigDecimal money = projectBorrowerPlanDetail.getMoney();
                RefundFeeDTO refundFeeDTO = new RefundFeeDTO();
                refundFeeDTO.setPeriod(projectBorrowerPlanNew.getPeriod());
                refundFeeDTO.setRefundDate(projectBorrowerPlanNew.getRefundDate());
                refundFeeDTO.setFeeType(projectBorrowerPlanDetail.getFeeType());
                refundFeeDTO.setRefundingMoney(money.subtract(refundMoney));
                resultList.add(refundFeeDTO);
            }
        }
        // 关键排序
        Collections.sort(resultList);
        return resultList;
    }

    /**
     * 返回可还（服务费或平台管理费）
     * @param borrowerPlanNewList
     * @param feeTypeEnum
     * @return
     */
    @Transactional(propagation = Propagation.MANDATORY)
    public List<RefundFeeDTO> getRefundFeeDTOList(List<ProjectBorrowerPlanNew> borrowerPlanNewList, FeeTypeEnum feeTypeEnum, BigDecimal availableAmount){
        List<RefundFeeDTO> refundingFeeDTOList = this.getRefundingFeeDTOList(borrowerPlanNewList, feeTypeEnum);
        return this.getRefundFeeDTOList(refundingFeeDTOList, availableAmount);
    }

    /**
     * 找出可还
     * @return
     */
    public List<RefundFeeDTO> getRefundFeeDTOList(List<RefundFeeDTO> refundingFeeDTOList, BigDecimal availableAmount){
        List<RefundFeeDTO> resultList = new ArrayList<>();
        BigDecimal remainingAmount = availableAmount;// 分配剩余
        for (RefundFeeDTO refundingFeeDTO : refundingFeeDTOList) {
            if(remainingAmount.compareTo(BigDecimal.ZERO) == 0){
                break;
            }
            RefundFeeDTO result = new RefundFeeDTO();
            result.setPeriod(refundingFeeDTO.getPeriod());
            result.setFeeType(refundingFeeDTO.getFeeType());
            result.setRefundDate(refundingFeeDTO.getRefundDate());
            if(remainingAmount.compareTo(refundingFeeDTO.getRefundingMoney()) >= 0){
                result.setRefundingMoney(refundingFeeDTO.getRefundingMoney());
                remainingAmount = remainingAmount.subtract(refundingFeeDTO.getRefundingMoney());
            }else{
                result.setRefundingMoney(remainingAmount);
                remainingAmount = BigDecimal.ZERO;
            }
            resultList.add(result);
        }
        return resultList;
    }


    /**
     * 应还金额处理
     * @param refundFeeDTOList
     */
    public RefundStateDTO convertToRefundFee(List<RefundFeeDTO> refundFeeDTOList){
        RefundStateDTO result = new RefundStateDTO();
        for (RefundFeeDTO refundFeeDTO : refundFeeDTOList) {
            BigDecimal refundingMoney = refundFeeDTO.getRefundingMoney();
            result.setAmount(refundingMoney.add(result.getAmount()));
            result.setReducedFee(refundFeeDTO.getReducedFee().add(result.getReducedFee()));
            if(FeeTypeEnum.CAPITAL.getValue().equals(refundFeeDTO.getFeeType())){
                result.setCapital(refundingMoney.add(result.getCapital()));
            } else if(FeeTypeEnum.INTEREST.getValue().equals(refundFeeDTO.getFeeType())){
                result.setInterest(refundingMoney.add(result.getInterest()));
            } else if(FeeTypeEnum.PENALTY.getValue().equals(refundFeeDTO.getFeeType())){
                result.setPenaltyFee(refundingMoney.add(result.getPenaltyFee()));
            } else if(FeeTypeEnum.MANAGEMENT_FEE.getValue().equals(refundFeeDTO.getFeeType())){
                result.setManageFee(refundingMoney.add(result.getManageFee()));
            } else if(FeeTypeEnum.GUARANTEE_FEE.getValue().equals(refundFeeDTO.getFeeType())){
                result.setGuaranteeFee(refundingMoney.add(result.getGuaranteeFee()));
            } else if(FeeTypeEnum.CONSULTING_FEE.getValue().equals(refundFeeDTO.getFeeType())){
                result.setConsultingFee(refundingMoney.add(result.getConsultingFee()));
            } else{
                throw new RuntimeException("未知的费用类型");
            }
        }
        return result;
    }

    private BigDecimal calculateInterestByDay(Project project, Date currentDate, List<Integer> periodList) {
        List<ProjectInvestorPlan> projectInvestorPlanList = projectInvestorPlanMapperExt.selectListByProjectIdAndPeriodList(project.getProjectId(), periodList);
        BigDecimal dayRate = project.getBorrowRate().divide(BigDecimal.valueOf(AmountHandleConstant.YEAR_OF_DAY), MathContext.DECIMAL128);

        BigDecimal interest = BigDecimal.ZERO;
        for(ProjectInvestorPlan projectInvestorPlan : projectInvestorPlanList){
            interest = interest.add(calculateByDay(projectInvestorPlan.getCapital(), dayRate, currentDate, project.getFullSuccessTime()));
        }
        return interest;
    }

    private BigDecimal calculateManageFeeByDay(Project project, Date currentDate) {
        BigDecimal dayRate = project.getManageRate().divide(BigDecimal.valueOf(project.getDeadline()), MathContext.DECIMAL128);
        return calculateByDay(project.getContractAmount(), dayRate, currentDate, project.getFullSuccessTime());
    }

    private BigDecimal calculateGuaranteeFeeByDay(Project project, Date currentDate) {
        BigDecimal dayRate = project.getGuaranteeRate().divide(BigDecimal.valueOf(project.getDeadline()), MathContext.DECIMAL128);
        return calculateByDay(project.getContractAmount(), dayRate, currentDate, project.getFullSuccessTime());
    }

    private BigDecimal calculateConsultingFeeByDay(Project project, Date currentDate) {
        BigDecimal dayRate = project.getConsultingRate().divide(BigDecimal.valueOf(project.getDeadline()), MathContext.DECIMAL128);
        return calculateByDay(project.getContractAmount(), dayRate, currentDate, project.getFullSuccessTime());
    }

    private BigDecimal calculateByDay(BigDecimal amount, BigDecimal dayRate, Date currentDate, Date fullSuccessDate) {
        logger.info("calculateByDay amount:{},dayRate:{},currentDate:{},fullSuccessDate:{}", amount, dayRate, currentDate, fullSuccessDate);
        int currentDeadline = RepaymentUtil.getOverdueDay(fullSuccessDate, currentDate);
        // 满标日当天进行还款计息一天
        currentDeadline = currentDeadline == 0 ? 1 : currentDeadline;
        logger.info("calculateByDay currentDeadline:{}", currentDeadline);

        BigDecimal result = amount.multiply(dayRate).multiply(new BigDecimal(currentDeadline)).setScale(AmountHandleConstant.CALCULATE_PRECISION_TWO, BigDecimal.ROUND_HALF_EVEN);
        logger.info("calculateByDay result:{}", result);
        return result;
    }
}
